// bitfile.c

#include "bitfile.h"

// Buffer size in bytes
#define BITBUF_SIZE 4096
// Buffer size in bits
#define BITBUF_SIZE_IN_BITS (BITBUF_SIZE * 8)

extern int verbose;

struct __bitbuf
{
	int first; // first valid bit in the buffer
	int empty; // first free bit in the buffer
	int mode; // 0 = READ; 1 = WRITE
	int file_descriptor; // OS file descriptor for sys. calls
	unsigned char buffer[BITBUF_SIZE];
};

// This utility-function is used to read a single bit inside a byte
unsigned READ_BIT(const unsigned char *base, unsigned int i)
{
	unsigned char mask;
	base += i >> 3;
	mask = 1 << (i & 7);
	return *base & mask;	// ritorna 0 o diverso da 0
}

// This utility-function is used to write a single bit inside a byte
void WRITE_BIT(unsigned char *base, unsigned int i, int val)
{
	// val puo' essere 0 o diverso da 0
	unsigned char mask, d;
	mask = val ? 1 << (i&7) : 0;
	d = base[i >> 3] & ~(1 << (i & 7));
	base[i >> 3] = d | mask;
}

// There is no need for comment for this one...
int min(int x1, int x2)
{
    return x1<=x2?x1:x2;
}

// This function opens a file given its path for reading or writing
// mode = 0: read
// mode = 1: write
BITFILE* bitfile_open(const char* filename,int mode)
{
	int fd = -1;
	BITFILE *bfp = NULL;
	
	// checks on filename and mode
	if(filename == NULL || filename[0] == '\0')
		return NULL;

	if(mode != 0 && mode != 1)
		return NULL;

	// file opening
        if(mode==0)
			fd = open((const char*)filename, O_RDONLY);
        else
			fd = open((const char*)filename, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);

	if(fd<0)
		return NULL;

	// descriptor allocation: calloc allocates and initializes to zero
	bfp = (BITFILE*)calloc(1, sizeof(BITFILE));
	if(bfp == NULL)
		close(fd);
	else
	{
		bfp->mode = mode;
		bfp->file_descriptor = fd;
	}

	// returning pointer
	return bfp;
}

// Copies "len" bits from bit #"src_offset" of src string to
// bit #"dst_offset" of dst string
void bit_copy(const unsigned char *src, int src_offset, unsigned char *dst, int dst_offset, int len)
{
	int i;
	for(i = 0; i < len; i++)
	{
		WRITE_BIT(dst, dst_offset++, READ_BIT(src, src_offset++));
	}
}

// Writes src_len bits read from src into the file pointed by BITFILE descriptor
int bitfile_write(BITFILE *bfp, const unsigned char *src, int src_len)
{
	int src_offset = 0;
	for(;src_len > 0;)
	{
		int len = min(src_len, BITBUF_SIZE_IN_BITS - bfp->empty);
		bit_copy(src, src_offset, bfp->buffer, bfp->empty, len);
		src_offset += len;
		bfp->empty += len;
		src_len -= len;
		
		if(bfp->empty == BITBUF_SIZE_IN_BITS)
		{
			/*int l = */write(bfp->file_descriptor, bfp->buffer, BITBUF_SIZE);
			bfp->empty = 0;
		}
	}
	return src_offset;
}

// Reads dst_len bits from the file pointed by BITFILE struct into dst
int bitfile_read(BITFILE *bfp, unsigned char *dst, int dst_len)
{
	int dst_offset = 0;
	int len;
	for(;dst_len > 0;)
	{
        	if(bfp->first == bfp->empty || bfp->first == BITBUF_SIZE_IN_BITS)
		{
			int l = (int)read(bfp->file_descriptor, bfp->buffer, BITBUF_SIZE);
			if(l<=0)
				break;
           		bfp->first = 0;
			bfp->empty = l * 8;
		}
		len = min(bfp->empty - bfp->first, dst_len);
		if(len==0)
		{
			break;
		}
		bit_copy(bfp->buffer, bfp->first, dst, dst_offset, len);
		bfp->first += len;
		dst_offset += len;
		dst_len -= len;
	}
	return dst_offset;
}

// This functions adds some padding to complete the last byte if not full,
// then closes the file descriptor and frees the allocated memory
int bitfile_close(BITFILE* bfp)
{
	int err = 0;
	if(bfp==NULL)
		return EINVAL;
	
	if(bfp->mode == 1 && bfp->empty > 0)
	{
		// it is necessary to write the last byte to the disk
		// compute the size to the next multiple of a byte; bfp->first is 0
		int n = (bfp->empty + 7) / 8;
		int l = (int)write(bfp->file_descriptor, bfp->buffer, n);
		if(n != l)
		{
			err = EINVAL;
		}
	}
	close(bfp->file_descriptor);
	memset(bfp, 0, sizeof(BITFILE));
	free(bfp);
	return err;
}

int bitfile_file_descriptor(BITFILE* bfd)
{
    return bfd->file_descriptor;
}

